<?php
/**
 * Pert Library
 * Postgre Classes
 *
 * @author	Emre Hasegeli <hasegeli@gmail.com>
 * @license	GNU General Public License v3
 */

namespace Pert;

class Postgre_connection
{
	private $dbName;

	private $host;

	private $port;

	private $user;

	private $password;

	const persistentConnection = false;

	protected function __construct($dbName,
			$host,
			$port,
			$user,
			$password)
	{
		$this->dbName= $dbName;

		$this->host= $host;

		$this->port= $port;

		$this->user= $user;

		$this->password= $password;
	}

	public function __ToString()
	{
		return $this->dbName;
	}

	private $connection;

	protected function selectConnection()
	{
		if($this->connection)
			return $this->connection;

		if(self::persistentConnection)
			if($this->connection = pg_pconnect('host=' . $this->host . ' port=' . $this->port . ' user=' . $this->user . ' password=' . $this->password . ' dbname=' . $this->dbName))
				return $this->connection;

		if($this->connection = pg_connect('host=' . $this->host . ' port=' . $this->port . ' user=' . $this->user . ' password=' . $this->password . ' dbname=' . $this->dbName))
			return $this->connection;

		throw new Postgre_exception($this,
				'Cannot connected to the database.');
	}

	protected function __destruct()
	{
		if(!self::persistentConnection)
			if(isset($this->connection))
				pg_close($this->connection);
	}
}

class Postgre extends Postgre_connection
{
	private $totalQueryCount;

	private $totalQueryTime;

	private $connectionTime;

	public static $Debug = false;

	public function __Construct($dbName,
			$host,
			$port,
			$user,
			$password)
	{
		parent::__construct($dbName,
			$host,
			$port,
			$user,
			$password);

		if(self::$Debug)
		{
			$this->totalQueryCount = 0;

			$this->totalQueryTime = 0;
		}
	}

	private function totalQueryTimeString()
	{
		if($this->totalQueryCount)
			return $this->totalQueryCount . ' Query executed in ' . $this->totalQueryTime . ' second on "' . $this . '" database.';

		return '';
	}
	
	public function __Destruct()
	{
		parent::__destruct();

		if(self::$Debug)
			echo $this->totalQueryTimeString();
	}

	private $queryArray = array();

	private function checkQueryIfExecuted($query)
	{
		foreach($this->queryArray AS $q)
			if($q == $query)
				echo "<p >Same query: '" . $q . "'</p>";

		$this->queryArray[] = $query;
	}

	private function startTime()
	{
		$microTime = microtime();
		
		$microTime = explode(' ',
				$microTime);
		
		$microTime = $microTime[1] + $microTime[0];
		
		$this->connectionTime = $microTime;
	}

	private function stopTime()
	{
		$microTime = microtime();
		
		$microTime = explode(' ',
				$microTime);
		
		$microTime = $microTime[1] + $microTime[0];

		$this->totalQueryCount++;

		$this->totalQueryTime += ($microTime - $this->connectionTime);
	}

	public function SelectResultResource($query)
	{
		if(self::$Debug)
		{
			$this->checkQueryIfExecuted($query);

			$this->startTime();
		}

		if(!($resultResource = pg_query($this->selectConnection(),
				$query)))
			throw new Postgre_exception($this,
					pg_last_error($this->selectConnection()));

		if(self::$Debug)
			$this->stopTime();

		return $resultResource;
	}

	public function Begin()
	{
		$this->SelectResultResource('BEGIN');
	}

	public function Commit()
	{
		$this->SelectResultResource('COMMIT');
	}

	public function Rollback()
	{
		$this->SelectResultResource('ROLLBACK');
	}

	public function SelectResultArrayArray($query)
	{
		$result = new Postgre_result($this->SelectResultResource($query));

		return $result->FetchedDataArrayArray();
	}

	public function SelectResultArray($query)
	{
		$resultArrayArray = $this->SelectResultArrayArray($query);

		if(isset($resultArrayArray[0]))
			return $resultArrayArray[0];
	}

	public function SelectResult($query)
	{
		$resultArray = $this->SelectResultArray($query);

		if(isset($resultArray[0]))
			return $resultArray[0];
	}

	public function SelectArrayInput(Array $inputArray)
	{
		$sQL = '(';

		$count = 0;

		foreach($inputArray AS $input)
		{
			if($count++)
				$sQL .= ', ';

			$sQL .= $this->SelectInput($input);
		}

		return $sQL . ')';
	}

	public function SelectInput($input)
	{
		if(is_array($input))
			return $this->SelectArrayInput($input);

		if(is_bool($input))
			if($input)
				return 'true';
			else
				return 'false';

		if($input instanceof \DateTime)
			return "'" . $input->format('Y-m-d') . "'";

		if(is_int($input))
			return $input;

		if(is_float($input))
			return $input;

		if(!$input)
			return 'NULL';
				
		return "'" . pg_escape_string($this->selectConnection(),
				get_magic_quotes_gpc() ? stripslashes($input) : $input) . "'";
	}
}

class Postgre_exception extends \Exception
{
	private $postgre;

	private $queryName;

	public function __construct(Postgre $postgre,
			$message,
			$queryName = NULL)
	{
		$this->postgre = $postgre;

		parent::__construct($message);

		$this->queryName = $queryName;
	}

	public function __ToString()
	{
		$string = 'Database (' . $this->postgre . ') exception occured';

		if($this->queryName)
			$string .= " in '" . $this->queryName . "' query";

		$string .= ': ' . $this->message;

		return $string;
	}
}

class Postgre_result
{
	private $resultResource;

	public function __Construct($resultResource)
	{
		$this->resultResource = $resultResource;
	}

	private static function fetchedArrayData($fieldType,
			$data)
	{
		$values = trim($data,
				'{}');

		$fetchedArrayData = array();

		foreach(explode(',',
				$values) AS $value)
			if($value == 'NULL')
				$fetchedArrayData[] = NULL;
			else
				$fetchedArrayData[] = self::fetchedData($fieldType,
						trim($value,
								'"'));

		if((count($fetchedArrayData) == 1) AND (!isset($fetchedArrayData[0])))
			return array();

		return $fetchedArrayData;
	}

	private static function fetchedData($fieldType,
			$data)
	{
		if($fieldType[0] == '_')
			return self::fetchedArrayData(ltrim($fieldType,
					'_'),
					$data);

		if($data)
		{
			if($fieldType == 'int4')
				return (int) $data;

			if($fieldType == 'bool')
			{
				if($data == 't')
					return true;
	
				if($data == 'f')
					return false;
			}

			if(($fieldType == 'timestamptz') OR ($fieldType == 'date'))
				return new Date($data);
	
			return $data;
		}
	}

	private static function columnName($column)
	{
		if(!is_int($column))
			$column = '"' . $column . '"';

		return $column;
	}

	private $fieldTypeArray = array();

	private function fieldType($fieldId)
	{
		if(empty($this->fieldTypeArray[$fieldId]))
		{
			if(!is_numeric($fieldId))
				$fieldId = pg_field_num($this->resultResource,
						self::columnName($fieldId));

			$this->fieldTypeArray[$fieldId] = pg_field_type($this->resultResource,
					$fieldId);
		}

		return $this->fieldTypeArray[$fieldId];
	}

	private function fetchedDataArray(Array $dataArray)
	{
		$fetchedDataArray = array();

		foreach($dataArray AS $fieldId => $data)
			$fetchedDataArray[$fieldId] = self::fetchedData($this->fieldType($fieldId),
					$data);

		return $fetchedDataArray;
	}

	public function FetchedDataArrayArray()
	{
		$dataArrayArray = array();

		while($dataArray = pg_fetch_array($this->resultResource))
			$dataArrayArray[] = $this->fetchedDataArray($dataArray);

		return $dataArrayArray;
	}
}
